package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

// Config 配置结构体
type Config struct {
	Filename    string `json:"filename"`
	Version     string `json:"version"`
	Installer   string `json:"installer"`
	TmpDir      string `json:"tmp_dir"`
	Description string `json:"description"`
	Author      string `json:"author"`
}

func main() {
	// 获取当前可执行文件路径
	exePath, err := os.Executable()
	if err != nil {
		fmt.Printf("Error getting executable path: %v\n", err)
		os.Exit(1)
	}

	// 读取配置（从ZIP部分之前）
	config, err := readConfig(exePath)
	if err != nil {
		fmt.Printf("Error reading config: %v\n", err)
		os.Exit(1)
	}

	// 计算临时目录路径
	tempDir, err := getTempDir(config)
	if err != nil {
		fmt.Printf("Error calculating temp directory: %v\n", err)
		os.Exit(1)
	}

	// 检查是否已经安装
	if isAlreadyInstalled(tempDir) {
		// 直接运行已安装的程序
		err = runInstalledApp(tempDir)
		if err != nil {
			fmt.Printf("Error running installed application: %v\n", err)
			os.Exit(1)
		}
		return
	}

	// 提取并安装程序
	err = extractAndInstall(exePath, tempDir)
	if err != nil {
		fmt.Printf("Error extracting and installing: %v\n", err)
		os.Exit(1)
	}

	// 运行安装的程序
	err = runInstalledApp(tempDir)
	if err != nil {
		fmt.Printf("Error running application: %v\n", err)
		os.Exit(1)
	}
}

// readConfig 从可执行文件中读取配置
func readConfig(exePath string) (*Config, error) {
	// 打开可执行文件
	file, err := os.Open(exePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open executable: %v", err)
	}
	defer file.Close()

	// 获取文件信息
	info, err := file.Stat()
	if err != nil {
		return nil, fmt.Errorf("failed to get file info: %v", err)
	}

	// 查找配置在文件中的位置（从文件末尾向前搜索）
	// 配置以特定标记开始和结束
	configStartMarker := []byte("{\n  \"main\":")
	configEndMarker := []byte("\n}")

	// 从文件末尾开始搜索配置开始标记
	buf := make([]byte, 1024)
	fileSize := info.Size()

	// 读取文件末尾1024字节
	readStart := fileSize - 1024
	if readStart < 0 {
		readStart = 0
	}

	_, err = file.ReadAt(buf, readStart)
	if err != nil && err != io.EOF {
		return nil, fmt.Errorf("failed to read file: %v", err)
	}

	// 查找配置开始标记
	startIndex := bytes.Index(buf, configStartMarker)
	if startIndex == -1 {
		// 如果没有找到配置，使用默认配置
		return getDefaultConfig(), nil
	}

	// 查找配置结束标记
	endIndex := bytes.Index(buf[startIndex:], configEndMarker)
	if endIndex == -1 {
		// 如果没有找到结束标记，使用默认配置
		return getDefaultConfig(), nil
	}

	// 提取配置JSON
	configData := buf[startIndex : startIndex+endIndex+len(configEndMarker)]

	// 解析配置
	var configDataMap map[string]interface{}
	err = json.Unmarshal(configData, &configDataMap)
	if err != nil {
		return nil, fmt.Errorf("failed to parse config JSON: %v", err)
	}

	// 从配置数据创建Config对象
	config := &Config{
		Filename:    getStringValue(configDataMap, "name", "app"),
		Version:     getStringValue(configDataMap, "version", "1.0.0"),
		Installer:   getStringValue(configDataMap, "installer", getDefaultTempDir()),
		TmpDir:      getStringValue(configDataMap, "tmp_dir", "ex_{filename}_{md5}"),
		Description: getStringValue(configDataMap, "description", "Application packaged with PyInstallerEx"),
		Author:      getStringValue(configDataMap, "author", "Unknown"),
	}

	return config, nil
}

// getStringValue 从map中获取字符串值，如果不存在则返回默认值
func getStringValue(data map[string]interface{}, key string, defaultValue string) string {
	if val, exists := data[key]; exists {
		if str, ok := val.(string); ok {
			return str
		}
	}
	return defaultValue
}

// getDefaultConfig 获取默认配置
func getDefaultConfig() *Config {
	return &Config{
		Filename:    "app",
		Version:     "1.0.0",
		Installer:   getDefaultTempDir(),
		TmpDir:      "ex_{filename}_{md5}",
		Description: "Application packaged with PyInstallerEx",
		Author:      "Unknown",
	}
}

// getTempDir 计算临时目录路径
func getTempDir(config *Config) (string, error) {
	// 计算MD5哈希用于唯一标识
	hash := md5.Sum([]byte(config.Filename + config.Version))
	hashStr := hex.EncodeToString(hash[:8]) // 使用前8位

	tmpDir := strings.Replace(config.TmpDir, "{filename}", config.Filename, -1)
	tmpDir = strings.Replace(tmpDir, "{md5}", hashStr, -1)

	baseDir := config.Installer
	if baseDir == "" {
		baseDir = getDefaultTempDir()
	}

	return filepath.Join(baseDir, tmpDir), nil
}

// getDefaultTempDir 获取系统默认临时目录
func getDefaultTempDir() string {
	tempDir := os.Getenv("TEMP")
	if tempDir == "" {
		tempDir = os.Getenv("TMP")
	}
	if tempDir == "" {
		tempDir = "/tmp"
	}
	return tempDir
}

// isAlreadyInstalled 检查是否已经安装
func isAlreadyInstalled(tempDir string) bool {
	// 检查主可执行文件是否存在
	mainExe := filepath.Join(tempDir, getMainExecutableName())
	_, err := os.Stat(mainExe)
	return err == nil
}

// getMainExecutableName 根据平台获取主可执行文件名
func getMainExecutableName() string {
	if runtime.GOOS == "windows" {
		return "main.exe"
	}
	return "main"
}

// extractAndInstall 从当前可执行文件中提取ZIP内容并安装
func extractAndInstall(exePath, tempDir string) error {
	// 打开当前可执行文件
	file, err := os.Open(exePath)
	if err != nil {
		return fmt.Errorf("failed to open executable: %v", err)
	}
	defer file.Close()

	// 查找ZIP文件的位置（简化版，实际需要更复杂的逻辑）
	// 这里假设ZIP文件附加在可执行文件末尾

	// 在创建新目录之前，清理同名的旧目录
	err = cleanupOldDirectories(tempDir)
	if err != nil {
		fmt.Printf("Warning: failed to cleanup old directories: %v\n", err)
	}

	// 创建临时目录
	err = os.MkdirAll(tempDir, 0755)
	if err != nil {
		return fmt.Errorf("failed to create temp directory: %v", err)
	}

	// 创建一个简单的测试可执行文件
	testFile := filepath.Join(tempDir, getMainExecutableName())
	err = createTestExecutable(testFile)
	if err != nil {
		return fmt.Errorf("failed to create test executable: %v", err)
	}

	fmt.Printf("Application installed to: %s\n", tempDir)
	return nil
}

// cleanupOldDirectories 清理同名的旧目录，确保只保留一份最新的文件
func cleanupOldDirectories(targetDir string) error {
	// 获取目标目录的父目录和基本名称
	parentDir := filepath.Dir(targetDir)
	baseName := filepath.Base(targetDir)

	// 解析目录名格式 ex_{filename}_{md5}，获取filename部分
	parts := strings.Split(baseName, "_")
	if len(parts) < 3 || parts[0] != "ex" {
		// 如果目录名格式不匹配，不执行清理操作
		return nil
	}

	// 构造匹配模式 ex_{filename}_*
	filename := parts[1]

	// 读取父目录中的所有条目
	entries, err := os.ReadDir(parentDir)
	if err != nil {
		return fmt.Errorf("failed to read directory: %v", err)
	}

	// 查找并删除匹配的目录（除了当前目标目录）
	for _, entry := range entries {
		if entry.IsDir() {
			dirName := entry.Name()
			// 检查目录名是否匹配模式且不是当前目标目录
			if strings.HasPrefix(dirName, fmt.Sprintf("ex_%s_", filename)) && dirName != baseName {
				// 删除匹配的目录
				dirPath := filepath.Join(parentDir, dirName)
				fmt.Printf("Removing old directory: %s\n", dirPath)
				err := os.RemoveAll(dirPath)
				if err != nil {
					fmt.Printf("Warning: failed to remove directory %s: %v\n", dirPath, err)
				}
			}
		}
	}

	return nil
}

// createTestExecutable 创建测试用的可执行文件
func createTestExecutable(path string) error {
	if runtime.GOOS == "windows" {
		// 创建一个简单的Go程序
		goContent := `package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Hello from PyInstallerEx!")
	fmt.Println("This is a test application.")
	fmt.Println("Exiting in 3 seconds...")
	time.Sleep(3 * time.Second)
}
`
		// 先创建一个临时的Go源文件
		tempDir := filepath.Dir(path)
		tempGoFile := filepath.Join(tempDir, "temp_main.go")
		err := os.WriteFile(tempGoFile, []byte(goContent), 0644)
		if err != nil {
			return err
		}

		// 使用Go编译器编译成真正的exe文件
		cmd := exec.Command("go", "build", "-o", path, tempGoFile)
		err = cmd.Run()

		// 删除临时Go文件
		os.Remove(tempGoFile)

		// 如果编译成功，直接返回
		if err == nil {
			return nil
		}

		// 如果编译失败，回退到创建PowerShell脚本
		content := "@echo off\r\n" +
			"powershell -Command \"Write-Host 'Hello from PyInstallerEx!' ; Write-Host 'This is a test application.' ; Write-Host 'Exiting in 3 seconds...' ; Start-Sleep -Seconds 3\"\r\n"
		return os.WriteFile(path, []byte(content), 0755)
	} else {
		// 对于Linux，创建一个简单的bash脚本
		content := "#!/bin/bash\n" +
			"echo \"Hello from PyInstallerEx!\"\n" +
			"echo \"This is a test application.\"\n" +
			"echo \"Exiting in 3 seconds...\"\n" +
			"sleep 3\n"
		return os.WriteFile(path, []byte(content), 0755)
	}
}

// runInstalledApp 运行已安装的程序
func runInstalledApp(tempDir string) error {
	mainExe := filepath.Join(tempDir, getMainExecutableName())

	// 检查文件是否存在
	if _, err := os.Stat(mainExe); os.IsNotExist(err) {
		return fmt.Errorf("executable not found: %s", mainExe)
	}

	cmd := exec.Command(mainExe)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin

	return cmd.Run()
}
